2 added 1042 characters in body

# JavaScript (Node.js), 167 bytes

s=>g=(r=c='',[p,...q]=s,w)=>w?0:p?(g(r+0,q,p=='*')+g(r+1,q,1/p),c==1):c-=-![...s].some((p,i)=>p>' '&&[-1,1,-q,-1-q,-2-q,q,q+1,q+2].map(j=>p-=~~r[i+j])|p,q=s.search
)


Try it online!

Though op say "input will always be rectangular, have at least one solution", false sample 3 doesn't match, so I still require 1 solution, not <2 solution

s=>(        // p.s. Here "block" can also mean \n
c=0,          // possible mine count
g=(           // recursive
r='',       // mine states
[p,...q]=s, // known info to check possible state for a block
w           // invert condition, stop if true
)=>
w?0:
p?(       // for each block
g(r+0,q,p=='*')+   // possibly not bomb if doesn't say so
g(r+1,q,1/p),      // number/newline can't be bomb
c==1               // only one bomb
):
c-=-![...s].some(  // no block doesn't satisfy
(p,i)=>
p>' '&& // \n don't mean number
// other symbols turn into NaN when counting
[-1,1,-q,-1-q,-2-q,q,q+1,q+2].map(j=>p-=~~r[i+j])
// subtract each neighbor, OOB = 0
|p,     // difference between intended and actual
q=s.search('\n') // how many blocks in a line
)
)


# JavaScript (Node.js), 167 bytes

s=>g=(r=c='',[p,...q]=s,w)=>w?0:p?(g(r+0,q,p=='*')+g(r+1,q,1/p),c==1):c-=-![...s].some((p,i)=>p>' '&&[-1,1,-q,-1-q,-2-q,q,q+1,q+2].map(j=>p-=~~r[i+j])|p,q=s.search
)


Try it online!

Though op say "input will always be rectangular, have at least one solution", false sample 3 doesn't match, so I still require 1 solution, not <2 solution

# JavaScript (Node.js), 167 bytes

s=>g=(r=c='',[p,...q]=s,w)=>w?0:p?(g(r+0,q,p=='*')+g(r+1,q,1/p),c==1):c-=-![...s].some((p,i)=>p>' '&&[-1,1,-q,-1-q,-2-q,q,q+1,q+2].map(j=>p-=~~r[i+j])|p,q=s.search
)


Try it online!

Though op say "input will always be rectangular, have at least one solution", false sample 3 doesn't match, so I still require 1 solution, not <2 solution

s=>(        // p.s. Here "block" can also mean \n
c=0,          // possible mine count
g=(           // recursive
r='',       // mine states
[p,...q]=s, // known info to check possible state for a block
w           // invert condition, stop if true
)=>
w?0:
p?(       // for each block
g(r+0,q,p=='*')+   // possibly not bomb if doesn't say so
g(r+1,q,1/p),      // number/newline can't be bomb
c==1               // only one bomb
):
c-=-![...s].some(  // no block doesn't satisfy
(p,i)=>
p>' '&& // \n don't mean number
// other symbols turn into NaN when counting
[-1,1,-q,-1-q,-2-q,q,q+1,q+2].map(j=>p-=~~r[i+j])
// subtract each neighbor, OOB = 0
|p,     // difference between intended and actual
q=s.search('\n') // how many blocks in a line
)
)

1

# JavaScript (Node.js), 167 bytes

s=>g=(r=c='',[p,...q]=s,w)=>w?0:p?(g(r+0,q,p=='*')+g(r+1,q,1/p),c==1):c-=-![...s].some((p,i)=>p>' '&&[-1,1,-q,-1-q,-2-q,q,q+1,q+2].map(j=>p-=~~r[i+j])|p,q=s.search
)


Try it online!

Though op say "input will always be rectangular, have at least one solution", false sample 3 doesn't match, so I still require 1 solution, not <2 solution